﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Diagnostics;
using System.IO;
using System.Xml;
using Flower.Directory.Scripting;
using Flower.Services.Data;
using Jurassic.Library;
using Jurassic;
using System.Text;

namespace Flower.Directory.Management.Scripting
{
    /// <summary>
    /// The object performing file system operations.
    /// </summary>
    public class FileSystemObject : ObjectInstance
    {
        public FileSystemObject(ScriptEngine engine)
            : base(engine)
        {
            PopulateFunctions();
        }

        /// <summary>
        /// Loads XmlObject from a file.
        /// </summary>
        /// <param name="fileName">The name of the XML file.</param>
        /// <param name="blobFormat">The format of the XmlObject BLOB.</param>
        /// <returns>The loaded XML object.</returns>
        [JSFunction(Name = "loadXml")]
        public XmlObject LoadXml(string fileName, int blobFormat)
        {
            try
            {
                using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                {
                    var xmlDoc = new XmlDocument();

                    xmlDoc.Load(stream);

                    return new XmlObject(Engine.Object.InstancePrototype, xmlDoc, (BlobFormat)blobFormat);
                }
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Writes an object to a file in a human readable format.
        /// 
        /// If the file already exists, it will be overwritten.
        /// </summary>
        /// <param name="fileName">The name of the target file.</param>
        /// <param name="obj">The object to write.</param>
        /// <param name="encoding">The encoding of the target file.</param>
        [JSFunction(Name = "print")]
        public void Print(string fileName, ObjectInstance obj, object encoding)
        {
            try
            {
                var enc = Encoding.GetEncoding
                (
                    (encoding != null && !(encoding is Undefined) && !(encoding is Null)) ?
                    encoding.ToString() :
                    "UTF-8"
                );

                using (var writer = new StreamWriter(fileName, true, enc))
                {
                    Printer.Print(writer, obj, false);
                    writer.Flush();
                }
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Writes an object to a file in a human readable format appending the new line char to the end.
        /// 
        /// If the file already exists, it will be overwritten.
        /// </summary>
        /// <param name="fileName">The name of the target file.</param>
        /// <param name="obj">The object to write.</param>
        /// <param name="encoding">The encoding of the target file.</param>
        [JSFunction(Name = "println")]
        public void PrintLn(string fileName, ObjectInstance obj, object encoding)
        {
            try
            {
                var enc = Encoding.GetEncoding
                (
                    (encoding != null && !(encoding is Undefined) && !(encoding is Null)) ?
                    encoding.ToString() :
                    "UTF-8"
                );

                using (var writer = new StreamWriter(fileName, true, enc))
                {
                    Printer.Print(writer, obj, true);
                    writer.Flush();
                }
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Checks whether a specified file exists.
        /// </summary>
        /// <param name="fileName">The name of the file to check.</param>
        /// <returns>True if the file exists; otherwise, false.</returns>
        [JSFunction(Name = "fileExists")]
        public bool FileExists(string fileName)
        {
            try
            {
                return File.Exists(fileName);
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Checks whether a specified directory exists.
        /// </summary>
        /// <param name="fileName">The name of the directory to check.</param>
        /// <returns>True if the directory exists; otherwise, false.</returns>
        [JSFunction(Name = "directoryExists")]
        public bool DirectoryExists(string fileName)
        {
            try
            {
                return System.IO.Directory.Exists(fileName);
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Returns an array of all the file names and directory names that match a search
        /// pattern in a specified path, and optionally searches subdirectories.
        /// </summary>
        /// <param name="path">The directory to search.</param>
        /// <param name="pattern">
        /// The string used to search for all files or directories that match its search pattern. 
        /// The default pattern is for all files and directories: "*".
        /// </param>
        /// <param name="deep">Whether to search subdirectories.</param>
        /// <returns>An array of file system entries that match the search criteria.</returns>
        [JSFunction(Name = "find")]
        public ArrayInstance Find(object path, string pattern, bool deep)
        {
            try
            {
                ArrayInstance result = Engine.Array.Construct();

                string pathToSearch =
                    (path != null && !(path is Undefined) && !(path is Null)) ?
                    path.ToString() :
                    ".";

                var entries =
                    System.IO.Directory.GetFileSystemEntries
                    (
                        pathToSearch,
                        pattern,
                        (deep) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly
                    );
                foreach (string entry in entries)
                {
                    result.Push(entry);
                }

                return result;
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Returns an array of all the file names and directory names in a directory.
        /// </summary>
        /// <param name="path">The directory to search.</param>
        /// <returns>An array of all file system entries in the directory.</returns>
        [JSFunction(Name = "dir")]
        public ArrayInstance Dir(object path)
        {
            try
            {
                ArrayInstance result = Engine.Array.Construct();

                string pathToSearch =
                    (path != null && !(path is Undefined) && !(path is Null)) ?
                    path.ToString() :
                    ".";

                foreach (string entry in System.IO.Directory.GetFileSystemEntries(pathToSearch))
                {
                    result.Push(entry);
                }

                return result;
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }

        /// <summary>
        /// Sets the working directory.
        /// </summary>
        /// <param name="wd">The working directory to set.</param>
        /// <returns>The new working directory.</returns>
        [JSFunction(Name = "cd")]
        public string ChangeDirectory(object wd)
        {
            try
            {
                if (wd != null && !(wd is Undefined) && !(wd is Null))
                {
                    System.IO.Directory.SetCurrentDirectory(wd.ToString());
                }

                return System.IO.Directory.GetCurrentDirectory();
            }
            catch (Exception ex)
            {
                throw new JavaScriptException(Engine, "Error", ex.Message, ex);
            }
        }
    }
}
